package Grantsfire::Schema::Feed::Validator;
use Grantsfire::Schema::Feed::Validator::Exceptions;
use DateTime;
use HTTP::Date;
use Moose;
use Regexp::Common 'URI';
use URI::Fetch;
use Cache::File;
use Text::Microformat;

has feed  => is => 'ro' => required => 1;
has cache => is => 'rw' => default => sub { __PACKAGE__->_setup_cache };

has content_type => is => 'rw';
has content      => is => 'rw';

has hgrants => is => 'rw' => isa => 'ArrayRef', default => sub{[]};

has warnings => is => 'rw' => isa => 'Int', default => 0;
has critical => is => 'rw' => isa => 'Int', default => 0;

sub BUILD {
  my ($self, $params) = @_;
  my $feed = $params->{feed};

  Grantsfire::Schema::Feed::Validator::Exceptions::Empty->throw if !$feed;
  Grantsfire::Schema::Feed::Validator::Exceptions::Malformed->throw unless $feed =~ /^$RE{URI}$/;
  $self->_setup_cache;
  $self->_fetch;
}

sub _setup_cache {
  return Cache::File->new(cache_root => '/tmp/feed_validator');
}

sub _fetch {
  my $self = shift;
  my $feed = $self->feed;
  my $res  = URI::Fetch->fetch($feed, Cache => $self->cache);
  Grantsfire::Schema::Feed::Validator::Exceptions::NotFound->throw(
    -text => URI::Fetch->errstr,
  ) unless $res;
  
  $self->$_($res->$_) for qw[content content_type];
}

sub validate {
  my $self = shift;
  $self->_parse_hgrants;
}

sub _validate_hgrant {
  my ($self, $data) = @_;
  $data->{warnings} = {};
  $data->{critical} = {};

  $data->{warnings}->{url} = $self->_feedback($data => "No URL for this hGrant.")
    if (!$data->{url});
  
  $data->{warnings}->{title} = $self->_feedback($data => "No Title for this hGrant.")
    if (!$data->{title});

  $data->{warnings}->{tags} = $self->_feedback($data => "No Tags for this hGrant.")
    if (! scalar(@{$data->{tags}}));

  $data->{warnings}->{period} = $self->_feedback($data => "No period information for this hGrant")
    if (!$data->{dtstart} && !$data->{dtend});

  $data->{critical}->{description} = $self->_feedback($data => "No description for this hGrant")
    unless $data->{description};
  
  $data->{critical}->{amount} = $self->_feedback($data => "No amount for this hGrant")
    unless $data->{amount};

  $data->{critical}->{currency} = $self->_feedback($data => "No currency for this hGrant")
    unless $data->{currency};

  $data->{critical}->{grantee} = $self->_feedback($data => "No grantee for this hGrant")
    unless $data->{grantee}->{org};

  $data->{critical}->{grantor} = $self->_feedback($data => "No grantor for this hGrant")
    unless $data->{grantor}->{org};


  $data->{has_warnings} = grep defined, values %{$data->{warnings}};
  $data->{has_critical} = grep defined, values %{$data->{critical}};

  $self->warnings($self->warnings + scalar(values(%{$data->{warnings}})));
  $self->critical($self->critical + scalar(values(%{$data->{critical}})));
}

sub _feedback {
  my ($self, $hgrant, $text) = @_;
  Grantsfire::Schema::Feed::Validator::Feedback->new({
    text   => $text,
    member => $hgrant,
  });
}

sub _parse_hgrants {
  my $self    = shift;
  my @hgrants = grep $_->isa('Text::Microformat::Element::hGrant'), eval {
    Text::Microformat->new(
      $self->content,
      content_type => $self->content_type
    )->find
  };
  $self->hgrants([map $self->_convert_hgrant($_), @hgrants]);
}

sub _convert_hgrant {
  my ($self, $hgrant) = @_;
  my %data;
  @data{qw[title description url dtstart dtend currency amount tags]} = (
    $hgrant->Get('title'),
    $self->_stringify($hgrant->Get('description')),
    $self->_stringify($hgrant->GetM('url')),
    (map do {
      my $epoch = str2time($hgrant->GetM("period.$_"));
      $epoch ? DateTime->from_epoch(epoch => $epoch) : undef
    }, qw[dtstart dtend]),
    (map $self->_stringify($hgrant->GetM("amount.$_")), qw[currency amount]),
    [map {tag => $_->HumanValue}, @{$hgrant->tags}],
  );
  
  for my $role_name (qw[grantor grantee grant]) {
    my $class = my $rel = $role_name;
  
    if ($role_name eq 'grant') {
      $class = 'geo-focus';
      $rel   = 'grant_vcard'
    }
    
    $data{$rel} = $self->_convert_role($hgrant => $class);
  }
  
  $self->_validate_hgrant(\%data);
  
  return \%data;
}

sub _convert_role {
  my ($self, $hgrant, $class) = @_;
  my %data;
  my $vcard_map = $self->_role_vcard_map->{$class};
  for (keys %{$vcard_map}) {
    $data{$vcard_map->{$_}} = $self->_stringify($hgrant->Get("$class.$_"));
    $data{url} = $self->_stringify($hgrant->GetM("$class.url"));
    $data{org} = $data{fn} = $self->_stringify(
      $hgrant->Get("$class.org") || $hgrant->Get("$class.fn")
    );
  }
  return \%data;
}

sub _role_vcard_map {
  my %map = (
    grantor => {qw[
      adr.post_office_box adr_post_office_box
      adr.extended_address adr_extended_address
      adr.street_address adr_street_address
      adr.locality adr_locality
      adr.region adr_regtion
      adr.postal_code adr_postal_code
      adr.country_name adr_country_name
      tel tel
      email email
    ]},
    'geo-focus' => {qw[
      locality adr_locality
      region adr_region
      postal_code adr_postal_code
      country adr_country_name
      tel tel
      emial email
    ]},
  );
  $map{grantee} = $map{grantor};
  return \%map;
}

sub _stringify {
    my ($self, $s) = @_;
    return $s if !defined $s;
    return $s->body if ref $s eq 'XML::Feed::Content';
    return "$s";
}

package Grantsfire::Schema::Feed::Validator::Feedback;
use Moose;

has text   => is => 'ro';
has member => is => 'ro';

1;
